﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RFEngine.Entities;
using RFEngine.Graphics;
using RFEngine.Graphics.Interfaces;
using RFEngine.Graphics.Effects;

namespace RFEngine
{
    public sealed class CameraLayer
    {
        #region Fields

        private bool _EnableParallaxingScroll;
        private Vector2 _ScrollSpeed = Vector2.Zero;

        private Camera _ParentCamera;
        private SpriteEffect _SpriteEffect;
        private BoundingFrustum _BoundingFrustum;
        private List<Camera> _Cameras = new List<Camera>();
        private List<IUpdateableEntity> _UpdateableEntities = new List<IUpdateableEntity>();
        private List<IDrawableEntity> _DrawableEntities = new List<IDrawableEntity>();

        private bool _UpdateEffect; 
        private Sprite _CurrentSprite;
        private ITextureable _Textureable;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or Sets a value indicating if this object should enable Parallax scrolling entity updates.
        /// </summary>
        public bool EnableParallaxScrolling
        {
            get { return _EnableParallaxingScroll; }
            set { _EnableParallaxingScroll = value; }
        }

        /// <summary>
        /// Gets or Sets the parallax scroll speed during update calls, if this layer has ParallaxScrolling enabled.
        /// </summary>
        public Vector2 ScrollSpeed
        {
            get { return _ScrollSpeed; }
            set { _ScrollSpeed = value; }
        }

        #endregion

        #region Methods

        #region Constructor(s)

        /// <summary>
        /// Constructs a new instance of this object.
        /// </summary>
        /// <param name="camera">The camera this layer belongs to</param>
        internal CameraLayer(Camera camera)
        {
            _SpriteEffect = new SpriteEffect(EngineServices.GraphicsDevice, null);

            _Cameras.Add(camera);
            EntityManager.AddCameraLayer(this);
        }

        #endregion
                
        #region Internal Methods

        /// <summary>
        /// Method for Adding a new camera to this layer.
        /// </summary>
        /// <param name="camera"></param>
        internal void AddCamera(Camera camera)
        {
            _Cameras.Add(camera);
        }

        /// <summary>
        /// Method for removing a camera from this layer.
        /// </summary>
        /// <param name="camera"></param>
        internal void RemoveCamera(Camera camera)
        {
            _Cameras.Remove(camera);

            if (_Cameras.Count == 0)
                EntityManager.RemoveCameraLayer(this);
        }

        /// <summary>
        /// Method for adding a new Entity to this layer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        internal void AddEntity<T>(T entity) where T : IUpdateableEntity
        {
            if (_UpdateableEntities.Contains(entity))
                return;

            _UpdateableEntities.Add(entity);
            if (entity is IDrawableEntity)
                _DrawableEntities.Add((IDrawableEntity)entity);
        }

        /// <summary>
        /// Method for removing an entity from this layer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        internal void RemoveEntity<T>(T entity) where T : IUpdateableEntity
        {
            _UpdateableEntities.Remove(entity);

            if (entity is IDrawableEntity)
                _DrawableEntities.Remove((IDrawableEntity)entity);
        }

        /// <summary>
        /// Method used for updating all updateable entities within this layer.
        /// </summary>
        internal void Update()
        {
           _UpdateableEntities.ForEach((entity) => entity.Update(TimeManager.LastUpdateTime));
        }

        /// <summary>
        /// Method used for drawing all drawable entities within this layer.
        /// </summary>
        internal void Draw()
        {
            /// Begin Sprite Effect.

            _SpriteEffect.Begin();

            _SpriteEffect.CurrentTechnique.Passes[0].Begin();

            _Cameras.ForEach((camera) =>
                {
                    _BoundingFrustum = camera.BoundingFrustum;

                    _SpriteEffect.View = camera.View;
                    _SpriteEffect.Projection = camera.Projection;
                    _SpriteEffect.CommitChanges();

                    if (_DrawableEntities.Count > 0)
                        camera.PrepareGraphicsDevice();

                    #region Drawing Code

                    /// Draw each drawable entity.
                    _DrawableEntities.ForEach((entity) =>
                        {
                            if (entity.IsInView(ref _BoundingFrustum))
                            {
                                if (entity is Sprite)
                                {
                                    _CurrentSprite = (entity as Sprite);

                                    if (_CurrentSprite.UsesCustomDraw())
                                    {
                                        /// If this sprite uses a custom draw method, End the Sprite effect used to draw sprites    
                                        _SpriteEffect.CurrentTechnique.Passes[0].End();
                                        _SpriteEffect.End();

                                        _CurrentSprite.CustomDraw();

                                        /// Once the custom draw method has been used, Begin the Generic SpriteEffect.
                                        _SpriteEffect.Begin();
                                        _SpriteEffect.CurrentTechnique.Passes[0].Begin();
                                    }

                                    else
                                    {
                                        if (!_SpriteEffect.EnableTextures)
                                        {
                                            _SpriteEffect.EnableTextures = true;
                                            _UpdateEffect = true;
                                        }

                                        if (_SpriteEffect.Texture != _CurrentSprite.Texture)
                                        {
                                            _SpriteEffect.Texture = _CurrentSprite.Texture;
                                            _UpdateEffect = true;
                                        }

                                        if (_UpdateEffect)
                                        {
                                            _UpdateEffect = false;
                                            _SpriteEffect.CommitChanges();
                                        }

                                        _CurrentSprite.Draw(_SpriteEffect);
                                    }
                                }
                                else
                                    if (entity is ITextureable)
                                    {
                                        _Textureable = (entity as ITextureable);

                                        if (!_SpriteEffect.EnableTextures)
                                        {
                                            _SpriteEffect.EnableTextures = true;
                                            _UpdateEffect = true;
                                        }

                                        if (_SpriteEffect.Texture != _Textureable.Texture)
                                        {
                                            _SpriteEffect.Texture = _Textureable.Texture;
                                            _UpdateEffect = true;
                                        }

                                        if (_UpdateEffect)
                                        {
                                            _UpdateEffect = false;
                                            _SpriteEffect.CommitChanges();
                                        }

                                        entity.Draw(_SpriteEffect);
                                    }
                                    else
                                    {
                                        if (_SpriteEffect.EnableTextures)
                                        {
                                            _SpriteEffect.EnableTextures = false;
                                            _SpriteEffect.CommitChanges();
                                        }

                                        entity.Draw(_SpriteEffect);
                                    }
                            }
                        }
                    );

                    #endregion
                }
            );            

            /// End the Sprite Effect.

            _SpriteEffect.CurrentTechnique.Passes[0].End();

            _SpriteEffect.End();
        }

        #endregion

        #endregion
    }
}
